﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers.CSharp;
using PathSplitterUtil;

namespace PathSplitter
{
    public static class Splitter
    {
        public static IEnumerable<string> GetPaths(string code, int maxLoopIterations = 3)
        {
            Compilation c = RosylnUtil.CompileCode(code);
            return GetPaths(c: c, maxLoopIterations: maxLoopIterations);
        }
        public static string GetCodeSpecializedToInput(string code, params object[] arguments)
        {
            // Brute force: just try every path. Slow for more than a few loop iterations.
            var res = RosylnUtil.RunCodeOnAllNoExceptions(
                GetPaths(code: code, maxLoopIterations: int.MaxValue),
                arguments: arguments)
               .First();
            /*
                // TODO Why are there duplicates?
               //.Distinct().Take(2).ToList();
            if (res.Count > 1)
            {
                throw new Exception("Path finding failed. Matched multiple paths.");
            }
            else if (res.Count == 1)
            {
                return res.Single().Item1;
            }
            else
            {
                throw new Exception("Path not found.");
            }*/
            return res.Item1;
        }
        public static IEnumerable<string> GetPaths(Compilation c, int maxLoopIterations = 3)
        {
            var root = c.SyntaxTrees.Single().GetRoot();
            var usings = string.Join("\n", root.ChildNodes().OfType<UsingDirectiveSyntax>()) + "\n";

            var method = root.ChildNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault();
            IEnumerable<string> paths;
            if (method == null)
            {
                var m = root.ChildNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
                if (m == null)
                {
                    throw new ArgumentException();
                }
                paths = GetPaths(m,
                                 maxLoopIterations: maxLoopIterations);
            }
            else
            {
                paths = GetPaths(method, maxLoopIterations: maxLoopIterations);
            }
            return paths.Select(path => (usings + path).Trim());
        }
        public static IEnumerable<string> GetPaths(this ClassDeclarationSyntax cls,
                                                   int maxLoopIterations = 3)
        {
            string clsStr = cls.ToString();
            clsStr = clsStr.Substring(0, clsStr.IndexOf('{')).Trim();
            var method = cls.Members.OfType<MethodDeclarationSyntax>().FirstOrDefault();
            if (method == null)
            {
                throw new ArgumentException();
            }
            foreach (var methodPath in method.GetPaths(maxLoopIterations: maxLoopIterations))
            {
                yield return clsStr + "\n{\n\t" + methodPath.Replace("\n", "\n\t").Trim() + "\n}";
            }
        }
        public static IEnumerable<string> GetPaths(this MethodDeclarationSyntax method,
                                                   int maxLoopIterations = 3)
        {
            foreach (var bodyPath in method.Body._GetPaths(maxLoopIterations: maxLoopIterations))
            {
                yield return string.Join("", method.Modifiers) + ""
                    + method.ReturnType + "" + method.Identifier
                    + method.ParameterList.ToString().TrimEnd() + "\n" + bodyPath;
            }
        }
        private static string ConditionToAssume(string condition)
        {
            // TODO Right form for assume?
            return "PexAssume.IsTrue(" + condition + ");";
        }
        public static string FlattenExpressionPath(IList<Tuple<string[], string>> paths,
                                                   Func<IList<string>, string> makeStatement)
        {
            string assumeStatements = string.Join("\n", paths.SelectMany(p => p.Item1)
                                                       .Select(ConditionToAssume));
            string statement = makeStatement(paths.Select(p => p.Item2).ToList());
            if (string.IsNullOrWhiteSpace(assumeStatements))
            {
                return statement;
            }
            else
            {
                return assumeStatements + "\n" + statement;
            }
        }
        public static string FlattenExpressionPath(
                Tuple<string[], string> path, Func<string, string> makeStatement)
        {
            return FlattenExpressionPath(path.Singleton(),
                (IList<string> strs) => makeStatement(strs[0]));
        }
        public static IEnumerable<string> FlattenExpressionPaths(
                ExpressionSyntax expression, Func<string, string> makeStatement)
        {
            return expression._GetPaths().Select(path =>
                FlattenExpressionPath(path, makeStatement));
        }
        public static IEnumerable<string> FlattenDeclPaths(
                VariableDeclarationSyntax decl, Func<string, string> makeStatement)
        {
            return decl._GetPaths().Select(path =>
                FlattenExpressionPath(path, makeStatement));
        }
        private static IEnumerable<string> _GetPaths(this StatementSyntax code, int maxLoopIterations = 3)
        {
            if (!code.ChildNodes().Any()
                || code is GotoStatementSyntax)
            {
                // leaf nodes
                yield return code.GetText();
            }
            else if (code is BlockSyntax)
            {
                var block = (BlockSyntax)code;
                var statementPaths = block.Statements.Select(s => s._GetPaths(maxLoopIterations: maxLoopIterations));
                foreach (var path in statementPaths.CartesianProductSingletons())
                {
                    yield return block.OpenBraceToken.ValueText
                        + (path.Any()
                            ? "\n\t" + string.Join("\n", path)
                                             .Replace("\n", "\n\t")
                            : "")
                        + "\n" + block.CloseBraceToken.ValueText;
                }
            }
            else if (code is IfStatementSyntax)
            {
                var ifStatement = (IfStatementSyntax)code;

                foreach (var condPath in ifStatement.Condition._GetPaths())
                {
                    string condition = condPath.Item2;
                    string preAssume = string.Join("\n", condPath.Item1);

                    string conditionPre = (string.IsNullOrWhiteSpace(preAssume) ? "" : preAssume + "\n")
                        + ConditionToAssume(condition) + "\n";
                    foreach (var path in ifStatement.Statement._GetPaths(maxLoopIterations: maxLoopIterations))
                    {
                        yield return conditionPre + path;
                    }
                    string notCondition = "!(" + condition + ")";
                    string notConditionPre = (string.IsNullOrWhiteSpace(preAssume) ? "" : preAssume + "\n")
                        + ConditionToAssume(notCondition) + "\n";
                    foreach (var path in (ifStatement.Else == null
                                        ? "".Singleton()
                                        : ifStatement.Else.Statement._GetPaths(maxLoopIterations: maxLoopIterations)))
                    {
                        yield return notConditionPre + path;
                    }
                }
            }
            else if (code is SwitchStatementSyntax)
            {
                var sw = (SwitchStatementSyntax)code;

                var expressionPaths = sw.Expression._GetPaths().ToList();
                foreach (string sectionsPath in
                    sw.Sections
                      .Select(s =>
                      {
                          var start = s.ToString();
                          start.Substring(0, start.IndexOf(':'));
                          return s.Statements
                               .Select(st =>
                                   st._GetPaths(maxLoopIterations: maxLoopIterations))
                               .CartesianProductSingletons()
                               .Select(sl => start + "\n\t" + string.Join("\n\t", sl));
                      }).CartesianProductSingletons()
                      .Select(sections => string.Join("\n", sections)))
                {
                    foreach (var exprPath in expressionPaths)
                    {
                        yield return string.Join("\n", exprPath.Item1) + "\n"
                            + sw.SwitchKeyword.ToString()
                            + sw.OpenParenToken
                            + exprPath.Item2
                            + sw.CloseParenToken
                            + sw.OpenBraceToken
                            + sectionsPath
                            + sw.CloseBraceToken;
                    }
                }
            }
            else if (code is WhileStatementSyntax || code is DoStatementSyntax
                    || code is ForStatementSyntax)
            {
                bool isDo = code is DoStatementSyntax;
                bool isFor = code is ForStatementSyntax;

                StatementSyntax statement;
                ExpressionSyntax conditionExpr;
                IList<string> forInit = "".Singleton();
                IList<string> forIncrementors = "".Singleton();

                if (code is WhileStatementSyntax)
                {
                    var whileStatement = (WhileStatementSyntax)code;
                    statement = whileStatement.Statement;
                    conditionExpr = whileStatement.Condition;
                }
                else if (code is DoStatementSyntax)
                {
                    var doStatement = (DoStatementSyntax)code;
                    statement = doStatement.Statement;
                    conditionExpr = doStatement.Condition;
                }
                else if (code is ForStatementSyntax)
                {
                    var forStatement = (ForStatementSyntax)code;
                    statement = forStatement.Statement;
                    conditionExpr = forStatement.Condition;
                    if (forStatement.Declaration != null)
                    {
                        forInit = FlattenDeclPaths(forStatement.Declaration,
                            declStr => declStr + ";\n").ToList();
                    }
                    if (forStatement.Incrementors != null)
                    {
                        forIncrementors = forStatement.Incrementors
                            .Select(e => FlattenExpressionPaths(e, exprStr => exprStr + ";"))
                            .CartesianProductSingletons()
                            .Select(exprs => "\n" + string.Join("\n", exprs))
                            .ToList();
                    }
                }
                else
                {
                    throw new NotImplementedException("Unsupported loop type: " + code.GetType() + ": " + code);
                }
                IList<string> bodyPaths = statement._GetPaths(maxLoopIterations: maxLoopIterations).ToList();
                foreach (var condPath in (conditionExpr == null ? new Tuple<string[], string>[] { null } : conditionExpr._GetPaths()))
                {
                    string conditionPre;
                    string notConditionPre;
                    if (condPath == null)
                    {
                        conditionPre = "";
                        notConditionPre = ConditionToAssume("false");
                    }
                    else
                    {
                        string condition = condPath.Item2;
                        string preAssume = string.Join("\n", condPath.Item1);
                        conditionPre = (string.IsNullOrWhiteSpace(preAssume) ? "" : preAssume + "\n")
                            + ConditionToAssume(condition) + "\n";
                        string notCondition = "!(" + condition + ")";
                        notConditionPre = (string.IsNullOrWhiteSpace(preAssume) ? "" : preAssume + "\n")
                            + ConditionToAssume(notCondition);
                    }

                    foreach (string loopPath in GetLoopPaths(maxLoopIterations: maxLoopIterations, isDo: isDo, forInit: forInit, bodyPaths: bodyPaths, forIncrementors: forIncrementors, notConditionPre: notConditionPre, conditionPre: conditionPre))
                    {
                        yield return loopPath;
                    }
                }
            }
            else if (code is ForEachStatementSyntax)
            {
                var forEach = (ForEachStatementSyntax)code;

                string loopVar = forEach.Identifier.GetText();
                string baseEnumeratorName = "__" + loopVar + "__enumerator_";
                string enumeratorName = baseEnumeratorName;
                int i = 0;
                string fullTreeText = code.SyntaxTree.GetRoot().GetText();
                while (fullTreeText.Contains(enumeratorName))
                {
                    enumeratorName = baseEnumeratorName + (i++);
                }

                IList<string> foreachInit = FlattenExpressionPaths(forEach.Expression,
                        enumerable => "var " + enumeratorName + " = (" + enumerable + ").AsEnumerable().GetEnumerator();")
                    .ToList();
                IList<string> bodyPaths = forEach.Statement._GetPaths(maxLoopIterations: maxLoopIterations).ToList();
                foreach (var init in foreachInit)
                {
                    for (int numIters = 0; numIters <= maxLoopIterations; numIters++)
                    {
                        string start = forEach.Type.GetText() + " " + loopVar + ";\n"
                            + (string.IsNullOrWhiteSpace(init) ? "" : init + "\n");
                        foreach (string loopBody in GetForeachLoopPaths(remainingIters: numIters, bodyPaths: bodyPaths, enumeratorName: enumeratorName, loopVar: loopVar))
                        {
                            yield return start + loopBody;
                        }
                    }
                }
            }
            else if (code is LocalDeclarationStatementSyntax)
            {
                var decl = (LocalDeclarationStatementSyntax)code;
                foreach (var path in decl.Declaration._GetPaths())
                {
                    yield return FlattenExpressionPath(path,
                        declStr => string.Join(" ", decl.Modifiers) + declStr + decl.SemicolonToken.GetText());
                }
            }
            else if (code is ReturnStatementSyntax)
            {
                var ret = (ReturnStatementSyntax)code;
                if (ret.Expression == null)
                {
                    yield return code.GetText().Trim();
                }
                else
                {
                    foreach (var path in FlattenExpressionPaths(ret.Expression,
                        exprStr => ret.ReturnKeyword.GetText()
                            + " " + exprStr + ret.SemicolonToken.GetText()))
                    {
                        yield return path;
                    }
                }
            }
            else if (code is YieldStatementSyntax)
            {
                var yieldStatement = (YieldStatementSyntax)code;
                if (yieldStatement.Expression == null)
                {
                    yield return code.GetText().Trim();
                }
                else
                {
                    foreach (var path in FlattenExpressionPaths(yieldStatement.Expression,
                        exprStr => yieldStatement.YieldKeyword.GetText()
                            + " " + yieldStatement.ReturnOrBreakKeyword.GetText()
                            + " " + exprStr + yieldStatement.SemicolonToken.GetText()))
                    {
                        yield return path;
                    }
                }
            }
            else if (code is ThrowStatementSyntax)
            {
                var throwStatement = (ThrowStatementSyntax)code;
                if (throwStatement.Expression == null)
                {
                    yield return code.GetText().Trim();
                }
                else
                {
                    foreach (var path in FlattenExpressionPaths(throwStatement.Expression,
                        exprStr => throwStatement.ThrowKeyword.GetText()
                            + " " + exprStr + throwStatement.SemicolonToken.GetText()))
                    {
                        yield return path;
                    }
                }
            }
            else if (code is ExpressionStatementSyntax)
            {
                var statement = (ExpressionStatementSyntax)code;
                foreach (var path in FlattenExpressionPaths(statement.Expression,
                        exprStr => exprStr + statement.SemicolonToken.GetText()))
                {
                    yield return path;
                }
            }
            else if (code is CheckedStatementSyntax)
            {
                var check = (CheckedStatementSyntax)code;
                foreach (var path in check.Block._GetPaths(maxLoopIterations: maxLoopIterations))
                {
                    yield return check.Keyword.GetText() + "\n" + path;
                }
            }
            else if (code is LabeledStatementSyntax)
            {
                var labeled = (LabeledStatementSyntax)code;
                foreach (var path in labeled.Statement._GetPaths(maxLoopIterations: maxLoopIterations))
                {
                    yield return labeled.Identifier.GetText()
                        + labeled.ColonToken
                        + path;
                }
            }
            else if (code is TryStatementSyntax)
            {
                var tryStatement = (TryStatementSyntax)code;

                var blockPaths = tryStatement.Block._GetPaths(maxLoopIterations: maxLoopIterations).ToList();
                var finallyPaths = tryStatement.Finally == null
                    ? "".Singleton().ToList()
                    : tryStatement.Finally.Block._GetPaths(maxLoopIterations: maxLoopIterations)
                        .Select(p => tryStatement.Finally.FinallyKeyword + p).ToList();
                var catchPaths = tryStatement.Catches
                    .Select(c =>
                    {
                        string catchPrefix = c.ToString();
                        catchPrefix = catchPrefix
                                     .Substring(0, catchPrefix.IndexOf('{'))
                                     .Trim()
                                     + " ";

                        return c.Block._GetPaths(maxLoopIterations: maxLoopIterations)
                            .Select(p => catchPrefix + p);
                    })
                    .CartesianProductSingletons();

                foreach (var catchPath in catchPaths)
                {
                    foreach (var finallyPath in finallyPaths)
                    {
                        foreach (var blockPath in blockPaths)
                        {
                            yield return (tryStatement.TryKeyword.GetText() + " "
                                + blockPath.Trim() + " "
                                + string.Join(" ", catchPath)
                                + finallyPath).Trim();
                        }
                    }
                }
            }
            else
            {
                throw new NotImplementedException("Unknown syntax node type: " + code.GetType() + ": " + code);
                //return new[] { code.ToString() };
            }
        }

        private static IEnumerable<string> GetLoopPaths(int maxLoopIterations, bool isDo, IList<string> forInit, IList<string> bodyPaths, IList<string> forIncrementors, string notConditionPre, string conditionPre)
        {
            for (int numIters = 0; numIters <= maxLoopIterations - (isDo ? 1 : 0); numIters++)
            {
                foreach (var init in forInit)
                {
                    foreach (var doBodyPath in (isDo ? bodyPaths : "".Singleton()))
                    {
                        string start = init + (isDo ? doBodyPath + "\n" : "");
                        foreach (var loop in GetLoopPaths(remainingIters: numIters, bodyPaths: bodyPaths, forIncrementors: forIncrementors, notConditionPre: notConditionPre, conditionPre: conditionPre))
                        {
                            yield return start + loop;
                        }
                    }
                }
            }
        }
        private static IEnumerable<string> GetLoopPaths(int remainingIters, IList<string> bodyPaths, IList<string> forIncrementors, string notConditionPre, string conditionPre)
        {
            foreach (var inc in forIncrementors)
            {
                foreach (var bodyPath in bodyPaths)
                {
                    if (remainingIters == 0)
                    {
                        yield return notConditionPre;
                    }
                    else
                    {
                        string thisIter = conditionPre + bodyPath + inc + "\n";
                        foreach (string rest in GetLoopPaths(remainingIters: remainingIters - 1, bodyPaths: bodyPaths, forIncrementors: forIncrementors, notConditionPre: notConditionPre, conditionPre: conditionPre))
                        {
                            yield return thisIter + rest;
                        }
                    }
                }
            }
        }
        private static IEnumerable<string> GetForeachLoopPaths(int remainingIters, IList<string> bodyPaths, string enumeratorName, string loopVar)
        {
            foreach (var body in bodyPaths)
            {
                if (remainingIters == 0)
                {
                    yield return ConditionToAssume("!" + enumeratorName + ".MoveNext()");
                }
                else
                {
                    string thisIter = ConditionToAssume(enumeratorName + ".MoveNext()")
                        + "\n" + loopVar + " = " + enumeratorName + ".Current;\n"
                        + body + "\n";
                    foreach (string rest in GetForeachLoopPaths(remainingIters: remainingIters - 1, bodyPaths: bodyPaths, enumeratorName: enumeratorName, loopVar: loopVar))
                    {
                        yield return thisIter + rest;
                    }
                }
            }
        }

        private static IEnumerable<Tuple<string[], string>> _GetPaths(this ExpressionSyntax code)
        {
            if (!code.ChildNodes().Any()
                || code is DefaultExpressionSyntax
                || code is TypeOfExpressionSyntax
                || code is TypeSyntax
                // TODO Right way to handle lambdas?
                || code is SimpleLambdaExpressionSyntax
                || code is ParenthesizedLambdaExpressionSyntax
                || code is AnonymousMethodExpressionSyntax
                // TODO Related... right way to handle LINQ queries?
                || code is QueryExpressionSyntax
                )
            {
                // leaf nodes
                yield return Tuple.Create(new string[0], code.GetText());
            }
            else if (code is ConditionalExpressionSyntax)
            {
                var cond = (ConditionalExpressionSyntax)code;
                foreach (var condPath in cond.Condition._GetPaths())
                {
                    string condition = condPath.Item2.Trim();
                    foreach (var path in cond.WhenTrue._GetPaths())
                    {
                        yield return Tuple.Create(condPath.Item1.Concat(condition.Cons(path.Item1)).ToArray(),
                                                  path.Item2);
                    }
                    string not_condition = "!(" + condition + ")";
                    foreach (var path in cond.WhenFalse._GetPaths())
                    {
                        yield return Tuple.Create(condPath.Item1.Concat(not_condition.Cons(path.Item1)).ToArray(),
                                                  path.Item2);
                    }
                }
            }
            else if (code is PrefixUnaryExpressionSyntax)
            {
                var pre = (PrefixUnaryExpressionSyntax)code;
                foreach (var path in pre.Operand._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              pre.OperatorToken.GetText() + path.Item2);
                }
            }
            else if (code is PostfixUnaryExpressionSyntax)
            {
                var post = (PostfixUnaryExpressionSyntax)code;
                foreach (var path in post.Operand._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              path.Item2 + post.OperatorToken.GetText());
                }
            }
            else if (code is ParenthesizedExpressionSyntax)
            {
                var expr = (ParenthesizedExpressionSyntax)code;
                foreach (var path in expr.Expression._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              expr.OpenParenToken + path.Item2 + expr.CloseParenToken.GetText());
                }
            }
            else if (code is CastExpressionSyntax)
            {
                var cast = (CastExpressionSyntax)code;
                foreach (var path in cast.Expression._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              cast.OpenParenToken.ToString()
                                                + cast.Type + cast.CloseParenToken
                                                + path.Item2);
                }
            }
            else if (code is BinaryExpressionSyntax)
            {
                var bin = (BinaryExpressionSyntax)code;
                foreach (var pathParts in new[] { bin.Left, bin.Right }.Select(o => o._GetPaths()).CartesianProductSingletons())
                {
                    yield return Tuple.Create(pathParts.SelectMany(p => p.Item1).ToArray(),
                        pathParts[0].Item2
                        + (bin.ToString().Contains(" " + bin.OperatorToken.GetText()) ? " " : "")
                        + bin.OperatorToken.GetText()
                        + (bin.ToString().Contains(bin.OperatorToken.GetText() + " ") ? " " : "")
                        + pathParts[1].Item2);
                }
            }
            else if (code is MemberAccessExpressionSyntax)
            {
                var mem = (MemberAccessExpressionSyntax)code;
                foreach (var path in mem.Expression._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              path.Item2 + mem.OperatorToken + mem.Name);
                }
            }
            else if (code is InvocationExpressionSyntax)
            {
                var call = (InvocationExpressionSyntax)code;
                var targets = call.Expression._GetPaths().ToList();
                var argumentLists = call.ArgumentList.Arguments.Select(a => a._GetPaths()).CartesianProductSingletons();

                foreach (var argumentList in argumentLists)
                {
                    foreach (var target in targets)
                    {
                        yield return Tuple.Create(target.Item1.Concat(argumentList.SelectMany(a => a.Item1)).ToArray(),
                            target.Item2
                            + call.ArgumentList.OpenParenToken
                            + string.Join(", ", argumentList.Select(a => a.Item2))
                            + call.ArgumentList.CloseParenToken);
                    }
                }
            }
            else if (code is ObjectCreationExpressionSyntax)
            {
                var newObj = (ObjectCreationExpressionSyntax)code;
                var argumentLists = newObj.ArgumentList.Arguments.Select(a => a._GetPaths()).CartesianProductSingletons();
                var initializerPaths = newObj.Initializer == null
                    ? Tuple.Create(new string[0], "").Singleton().ToList()
                    : newObj.Initializer._GetPaths().ToList();
                if (newObj.Initializer != null)
                {
                    throw new NotImplementedException("Object initializered unsupported in " + newObj);
                }

                foreach (var argumentList in argumentLists)
                {
                    foreach (var init in initializerPaths)
                    {
                        yield return Tuple.Create(argumentList.SelectMany(a => a.Item1).Concat(init.Item1).ToArray(),
                            newObj.NewKeyword.ToString()
                            + newObj.Type.ToString()
                            + newObj.ArgumentList.OpenParenToken
                            + string.Join(", ", argumentList.Select(a => a.Item2))
                            + newObj.ArgumentList.CloseParenToken);
                    }
                }
            }
            else if (code is AnonymousObjectCreationExpressionSyntax)
            {
                var newObj = (AnonymousObjectCreationExpressionSyntax)code;
                var initializerPaths = newObj.Initializers
                    .Select(initializer => {
                        string prefix = initializer.NameEquals == null
                            ? ""
                            : initializer.NameEquals.ToString();
                        return initializer.Expression._GetPaths()
                            .Select(path => Tuple.Create(path.Item1, prefix + path.Item2));
                    })
                    .CartesianProductSingletons()
                    .Select(inits => Tuple.Create(inits.SelectMany(i => i.Item1).ToArray(),
                                                  string.Join(", ", inits.Select(i => i.Item2))))
                    .ToList();

                foreach (var init in initializerPaths)
                {
                    yield return Tuple.Create(init.Item1,
                        newObj.NewKeyword.ToString()
                        + newObj.OpenBraceToken.ToString()
                        + init.Item2
                        + newObj.CloseBraceToken.ToString());
                }
            }
            else if (code is ElementAccessExpressionSyntax)
            {
                var access = (ElementAccessExpressionSyntax)code;
                var targets = access.Expression._GetPaths().ToList();
                var argumentLists = access.ArgumentList.Arguments.Select(a => a._GetPaths()).CartesianProductSingletons();

                foreach (var argumentList in argumentLists)
                {
                    foreach (var target in targets)
                    {
                        yield return Tuple.Create(target.Item1.Concat(argumentList.SelectMany(a => a.Item1)).ToArray(),
                            target.Item2
                            + access.ArgumentList.OpenBracketToken
                            + string.Join(", ", argumentList.Select(a => a.Item2))
                            + access.ArgumentList.CloseBracketToken);
                    }
                }
            }
            else if (code is ArrayCreationExpressionSyntax)
            {
                var ac = (ArrayCreationExpressionSyntax)code;
                var typePaths = ac.Type._GetPaths().ToList();
                var initializerPaths = ac.Initializer == null
                    ? Tuple.Create(new string[0], "").Singleton().ToList()
                    : ac.Initializer._GetPaths().ToList();

                foreach (var typePath in typePaths)
                {
                    foreach (var init in initializerPaths)
                    {
                        yield return Tuple.Create(typePath.Item1.Concat(init.Item1).ToArray(),
                            ac.NewKeyword.ToString() + typePath.Item2 + init.Item2);
                    }
                }
            }
            else if (code is ImplicitArrayCreationExpressionSyntax)
            {
                var ac = (ImplicitArrayCreationExpressionSyntax)code;
                var initializerPaths = ac.Initializer == null
                    ? Tuple.Create(new string[0], "").Singleton().ToList()
                    : ac.Initializer._GetPaths().ToList();

                foreach (var init in initializerPaths)
                {
                    yield return Tuple.Create(init.Item1,
                        ac.NewKeyword.ToString()
                        + ac.OpenBracketToken + ac.CloseBracketToken
                        + init);
                }
            }
            else if (code is ArrayTypeSyntax)
            {
                var at = (ArrayTypeSyntax)code;
                var rankPaths = at.RankSpecifiers
                        .Select(r =>
                            r.Sizes.Select(s => s._GetPaths())
                            .CartesianProductSingletons()
                            .Select(sizeList =>
                                Tuple.Create(sizeList.SelectMany(s => s.Item1),
                                    r.OpenBracketToken
                                    + string.Join(", ",
                                        sizeList.SelectMany(s => s.Item2))
                                    + r.CloseBracketToken))
                            .ToList())
                        .CartesianProductSingletons();

                foreach (var rankList in rankPaths)
                {
                    yield return Tuple.Create(rankList.SelectMany(s => s.Item1).ToArray(),
                                              at.PlainName
                                              + string.Concat(rankList.SelectMany(s => s.Item2)));
                }
            }
            else if (code is InitializerExpressionSyntax)
            {
                var init = (InitializerExpressionSyntax)code;
                foreach (var exprs in init.Expressions.Select(e => e._GetPaths()).CartesianProductSingletons())
                {
                    yield return Tuple.Create(exprs.SelectMany(e => e.Item1).ToArray(),
                        string.Join(", ", exprs.Select(e => e.Item2)));
                }
            }
            else if (code is CheckedExpressionSyntax)
            {
                var check = (CheckedExpressionSyntax)code;
                foreach (var path in check.Expression._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              check.Keyword.GetText()
                                              + check.OpenParenToken.GetText()
                                              + path.Item2
                                              + check.CloseParenToken.GetText());
                }
            }
            else
            {
                throw new NotImplementedException("Unknown expression type: " + code.GetType() + ": " + code);
            }
        }
        private static IEnumerable<Tuple<string[], string>> _GetPaths(this ArgumentSyntax arg)
        {
            foreach (var path in arg.Expression._GetPaths())
            {
                yield return Tuple.Create(path.Item1,
                                          (arg.NameColon == null ? "" : arg.NameColon.ToString())
                                          + arg.RefOrOutKeyword.ToString()
                                          + path.Item2);
            }
        }
        private static IEnumerable<Tuple<string[], string>> _GetPaths(this VariableDeclarationSyntax decl)
        {
            foreach (var pathParts in decl.Variables.Select(v => v._GetPaths()).CartesianProductSingletons())
            {
                yield return Tuple.Create(pathParts.SelectMany(p => p.Item1).ToArray(),
                                          decl.Type.GetText() + " " + string.Join(", ",
                                              pathParts.Select(p => p.Item2)));
            }
        }
        private static IEnumerable<Tuple<string[], string>> _GetPaths(this VariableDeclaratorSyntax decl)
        {
            if (decl.Initializer == null)
            {
                yield return Tuple.Create(new string[0], decl.Identifier.GetText());
            }
            else
            {
                foreach (var path in decl.Initializer._GetPaths())
                {
                    yield return Tuple.Create(path.Item1,
                                              decl.Identifier + path.Item2);
                }
            }
        }
        private static IEnumerable<Tuple<string[], string>> _GetPaths(this EqualsValueClauseSyntax decl)
        {
            foreach (var path in decl.Value._GetPaths())
            {
                yield return Tuple.Create(path.Item1,
                                          decl.EqualsToken + path.Item2);
            }
        }
    }
}
